﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Kokomo.Lexical.Expressions;
using System.Reflection;

using LinqExpressions = System.Linq.Expressions;
using LinqExpression = System.Linq.Expressions.Expression;
using Kokomo.PeInspector.ObjectGraphs;
using Kokomo.Lexical.Coding;
using Kokomo.PeInspector.Reflection.Query;
using System.ComponentModel;
using Kokomo.Mirror.CliMetadata;

namespace Kokomo.PeInspector.Reflection.Disassembly
{
	class LinqConverter
	{
		static LinqConverter()
		{
			InitializeTypedConverters();
			InitializeMethodConverters();
		}

		public static bool TryGetMethodCallInfo(Expression expression, out MethodInfo method, out Expression[] arguments)
		{
			BoundFunctionCallExpression functionCall = expression as BoundFunctionCallExpression;
			if (functionCall != null)
			{
				BoundMethodCallFunction methodCall = functionCall.Function as BoundMethodCallFunction;

				if (methodCall != null)
				{
					method = methodCall.Method;
					arguments = functionCall.Arguments.ToArray();
					return true;
				}
			}

			method = null;
			arguments = null;
			return false;
		}
		//private static IEnumerable<Expression> TryRaiseLinqExpressions(IEnumerable<Expression> expressions)
		//{
		//    List<Expression> raisedExpressions = new List<Expression>();
		//    foreach (var expression in expressions)
		//    {
		//        Expression raised = TryRaiseLinqExpression(expression, parameterTable);
		//        if (raised == null) return null;
		//        raisedExpressions.Add(raised);
		//    }

		//    return raisedExpressions;
		//}

		#region Expressions
		#region Method converters
		static IDictionary<MethodInfo, Delegate> _methodConverters;
		static void InitializeMethodConverters()
		{
			Dictionary<MethodInfo, Delegate> converters = new Dictionary<MethodInfo, Delegate>(MemberEqualityComparer.Instance);

			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Add).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertAddExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Add).Method] = (Func<LinqConversionContext, Expression, Expression, MethodInfo, Expression>)ConvertAddExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.And).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertAndExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.And).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertAndExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.AndAlso).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertAndAlsoExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.AndAlso).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertAndAlsoExpression;
			converters[((Func<LinqExpression, MethodInfo, IEnumerable<LinqExpression>, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, Expression, MethodInfo, IEnumerable<Expression>, Expression>)ConvertCallExpression;
			converters[((Func<LinqExpression, MethodInfo, LinqExpression, LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression, Expression, Expression, Expression>)ConvertCallExpression;
			converters[((Func<LinqExpression, MethodInfo, LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression, Expression, Expression>)ConvertCallExpression;
			converters[((Func<LinqExpression, MethodInfo, LinqExpression[], LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, Expression, MethodInfo, IEnumerable<Expression>, Expression>)ConvertCallExpression;
			converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertCallExpression;
			converters[((Func<MethodInfo, IEnumerable<LinqExpression>, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, MethodInfo, IEnumerable<Expression>, Expression>)ConvertCallExpression;
			converters[((Func<MethodInfo, LinqExpression[], LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, MethodInfo, IEnumerable<Expression>, Expression>)ConvertCallExpression;
			converters[((Func<MethodInfo, LinqExpression, LinqExpression, LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, MethodInfo, Expression, Expression, Expression, Expression, Expression>)ConvertCallExpression;
			converters[((Func<MethodInfo, LinqExpression, LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, MethodInfo, Expression, Expression, Expression, Expression>)ConvertCallExpression;
			converters[((Func<MethodInfo, LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, MethodInfo, Expression, Expression, Expression>)ConvertCallExpression;
			converters[((Func<MethodInfo, LinqExpression, LinqExpression>)LinqExpression.Call).Method] = (Func<LinqConversionContext, MethodInfo, Expression, Expression>)ConvertCallExpression;
			converters[((Func<object, Type, LinqExpression>)LinqExpression.Constant).Method] = (Func<LinqConversionContext, Expression, Type, Expression>)ConvertConstantExpression;
			converters[((Func<object, LinqExpression>)LinqExpression.Constant).Method] = (Func<LinqConversionContext, Expression, Expression>)ConvertConstantExpression;
			converters[((Func<LinqExpression, Type, MethodInfo, LinqExpression>)LinqExpression.Convert).Method] = (Func<LinqConversionContext, Expression, Type, MethodInfo, Expression>)ConvertConvertExpression;
			converters[((Func<LinqExpression, Type, LinqExpression>)LinqExpression.Convert).Method] = (Func<LinqConversionContext, Expression, Type, Expression>)ConvertConvertExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Divide).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertDivideExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Divide).Method] = (Func<LinqConversionContext, Expression, Expression, MethodInfo, Expression>)ConvertDivideExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Equal).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertEqualExpression;
			converters[((Func<LinqExpression, FieldInfo, LinqExpression>)LinqExpression.Field).Method] = (Func<LinqConversionContext, Expression, FieldInfo, Expression>)ConvertFieldExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.GreaterThan).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertGreaterThanExpression;
			converters[((Func<LinqExpression, LinqExpression, bool, MethodInfo, LinqExpression>)LinqExpression.GreaterThan).Method] = (Func<LinqConversionContext, Expression, Expression, bool, MethodInfo, Expression>)ConvertGreaterThanExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.GreaterThanOrEqual).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertGreaterThanOrEqualExpression;
			converters[((Func<LinqExpression, LinqExpression, bool, MethodInfo, LinqExpression>)LinqExpression.GreaterThanOrEqual).Method] = (Func<LinqConversionContext, Expression, Expression, bool, MethodInfo, Expression>)ConvertGreaterThanOrEqualExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.LeftShift).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertLeftShiftExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.LeftShift).Method] = (Func<LinqConversionContext, Expression, Expression, MethodInfo, Expression>)ConvertLeftShiftExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.LessThan).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertLessThanExpression;
			converters[((Func<LinqExpression, LinqExpression, bool, MethodInfo, LinqExpression>)LinqExpression.LessThan).Method] = (Func<LinqConversionContext, Expression, Expression, bool, MethodInfo, Expression>)ConvertLessThanExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.LessThanOrEqual).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertLessThanOrEqualExpression;
			converters[((Func<LinqExpression, LinqExpression, bool, MethodInfo, LinqExpression>)LinqExpression.LessThanOrEqual).Method] = (Func<LinqConversionContext, Expression, Expression, bool, MethodInfo, Expression>)ConvertLessThanOrEqualExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Modulo).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertModuloExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Modulo).Method] = (Func<LinqConversionContext, Expression, Expression, MethodInfo, Expression>)ConvertModuloExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Multiply).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertMultiplyExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Multiply).Method] = (Func<LinqConversionContext, Expression, Expression, MethodInfo, Expression>)ConvertMultiplyExpression;
			converters[((Func<LinqExpression, LinqExpression>)LinqExpression.Negate).Method] = (Func<LinqConversionContext, Expression, Expression>)ConvertNegateExpression;
			converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Negate).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertNegateExpression;
			converters[((Func<LinqExpression, LinqExpression>)LinqExpression.Not).Method] = (Func<LinqConversionContext, Expression, Expression>)ConvertNotExpression;
			converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Not).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertNotExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.NotEqual).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertNotEqualExpression;
			converters[((Func<LinqExpression, LinqExpression, bool, MethodInfo, LinqExpression>)LinqExpression.NotEqual).Method] = (Func<LinqConversionContext, Expression, Expression, bool, MethodInfo, Expression>)ConvertNotEqualExpression;
			converters[((Func<LinqExpression, LinqExpression>)LinqExpression.OnesComplement).Method] = (Func<LinqConversionContext, Expression, Expression>)ConvertOnesComplementExpression;
			converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.OnesComplement).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertOnesComplementExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Or).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertOrExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Or).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertOrExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.OrElse).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertOrElseExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.OrElse).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertOrElseExpression;
			//converters[((Func<LinqExpression, LinqExpression>)LinqExpression.PostDecrementAssign).Method] = (Func<LinqConversionContext, Expression, Expression>)ConvertPostDecrementAssignExpression;
			//converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.PostDecrementAssign).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertPostDecrementAssignExpression;
			//converters[((Func<LinqExpression, LinqExpression>)LinqExpression.PostIncrementAssign).Method] = (Func<LinqConversionContext, Expression, Expression>)ConvertPostIncrementAssignExpression;
			//converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.PostIncrementAssign).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertPostIncrementAssignExpression;
			//converters[((Func<LinqExpression, LinqExpression>)LinqExpression.PreDecrementAssign).Method] = (Func<LinqConversionContext, Expression, Expression>)ConvertPreDecrementAssignExpression;
			//converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.PreDecrementAssign).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertPreDecrementAssignExpression;
			//converters[((Func<LinqExpression, LinqExpression>)LinqExpression.PreIncrementAssign).Method] = (Func<LinqConversionContext, Expression, Expression>)ConvertPreIncrementAssignExpression;
			//converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.PreIncrementAssign).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertPreIncrementAssignExpression;
			converters[((Func<LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Property).Method] = (Func<LinqConversionContext, Expression, MethodInfo, Expression>)ConvertPropertyExpression;

			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.RightShift).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertRightShiftExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.RightShift).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertRightShiftExpression;
			converters[((Func<LinqExpression, LinqExpression, LinqExpression>)LinqExpression.Subtract).Method] = (Func<LinqConversionContext, Expression, Expression, Expression>)ConvertSubtractExpression;
			converters[((Func<LinqExpression, LinqExpression, MethodInfo, LinqExpression>)LinqExpression.Subtract).Method] = (Func<LinqConversionContext, Expression, Expression, MethodInfo, Expression>)ConvertSubtractExpression;
			converters[((Func<LinqExpression, Type, LinqExpression>)LinqExpression.TypeIs).Method] = (Func<LinqConversionContext, Expression, Type, Expression>)ConvertTypeIsExpression;
			converters[((Func<LinqExpression, Type, LinqExpression>)LinqExpression.TypeAs).Method] = (Func<LinqConversionContext, Expression, Type, Expression>)ConvertTypeAsExpression;

			converters[((Func<ConstructorInfo, IEnumerable<LinqExpression>, IEnumerable<MemberInfo>, LinqExpression>)LinqExpression.New).Method] = (Func<LinqConversionContext, ConstructorInfo, IEnumerable<Expression>, IEnumerable<MemberInfo>, Expression>)ConvertNewExpression;
			converters[((Func<ConstructorInfo, IEnumerable<LinqExpression>, MemberInfo[], LinqExpression>)LinqExpression.New).Method] = (Func<LinqConversionContext, ConstructorInfo, IEnumerable<Expression>, IEnumerable<MemberInfo>, Expression>)ConvertNewExpression;

			_methodConverters = converters;
		}

		private static Expression ConvertTypeIsExpression(LinqConversionContext context, Expression expression, Type type)
		{
			return new TypeCheckExpression(expression, type);
		}
		private static Expression ConvertTypeAsExpression(LinqConversionContext context, Expression expression, Type type)
		{
			return new CastExpression(expression, type, true);
		}
		private static Expression ConvertAddExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left + right;
		}
		private static Expression ConvertAddExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.Addition, method);
		}
		private static Expression ConvertSubtractExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left - right;
		}
		private static Expression ConvertSubtractExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.Subtraction, method);
		}
		private static Expression ConvertGreaterThanExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left > right;
		}
		private static Expression ConvertGreaterThanExpression(LinqConversionContext context, Expression left, Expression right, bool isLifted, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.GreaterThan, method);
		}
		private static Expression ConvertGreaterThanOrEqualExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left >= right;
		}
		private static Expression ConvertGreaterThanOrEqualExpression(LinqConversionContext context, Expression left, Expression right, bool isLifted, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.GreaterThanOrEqualTo, method);
		}
		private static Expression ConvertLessThanExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left < right;
		}
		private static Expression ConvertLessThanExpression(LinqConversionContext context, Expression left, Expression right, bool isLifted, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.LessThan, method);
		}
		private static Expression ConvertLessThanOrEqualExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left <= right;
		}
		private static Expression ConvertLessThanOrEqualExpression(LinqConversionContext context, Expression left, Expression right, bool isLifted, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.LessThanOrEqualTo, method);
		}
		private static Expression ConvertAndExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left & right;
		}
		private static Expression ConvertAndExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.BitwiseAnd, method);
		}
		private static Expression ConvertAndAlsoExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return Expression.op_LogicalAnd(left, right);
		}
		private static Expression ConvertAndAlsoExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.LogicalAnd, method);
		}
		private static Expression ConvertOrExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left | right;
		}
		private static Expression ConvertOrExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.BitwiseOr, method);
		}
		private static Expression ConvertOrElseExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return Expression.op_LogicalAnd(left, right);
		}
		private static Expression ConvertOrElseExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.LogicalOr, method);
		}
		private static Expression ConvertCallExpression(LinqConversionContext context, Expression instance, MethodInfo method, IEnumerable<Expression> arguments)
		{
			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(method.Name, method, instance, MethodCallFunctionBehavior.Normal), arguments);

			return functionCall;
		}
		private static Expression ConvertCallExpression(
			LinqConversionContext context,
			Expression instance,
			MethodInfo method,
			Expression arg0,
			Expression arg1,
			Expression arg2
			)
		{
			Expression[] arguments = new Expression[]{
				arg0,
				arg1,
				arg2
			};

			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(
				method.Name,
				method,
				instance,
				MethodCallFunctionBehavior.Normal
				), arguments);

			return functionCall;
		}
		private static Expression ConvertCallExpression(LinqConversionContext context, Expression instance, MethodInfo method, Expression arg0, Expression arg1)
		{
			Expression[] allArguments = new Expression[]{
				arg0,
				arg1
			};

			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(
				method.Name,
				method,
				instance,
				MethodCallFunctionBehavior.Normal
				), allArguments);

			return functionCall;
		}
		private static Expression ConvertCallExpression(LinqConversionContext context, Expression instance, MethodInfo method)
		{
			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(
				method.Name,
				method,
				instance,
				MethodCallFunctionBehavior.Normal
				), new Expression[0]);

			return functionCall;
		}
		private static Expression ConvertCallExpression(LinqConversionContext context, MethodInfo method, IEnumerable<Expression> arguments)
		{
			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(
				method.Name,
				method,
				null,
				MethodCallFunctionBehavior.Normal
				), arguments);

			return functionCall;
		}
		private static Expression ConvertCallExpression(LinqConversionContext context, MethodInfo method, Expression arg0, Expression arg1, Expression arg2, Expression arg3)
		{
			Expression[] allArguments = new Expression[]{
				arg0,
				arg1,
				arg2,
				arg3
			};

			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(
				method.Name,
				method,
				null,
				MethodCallFunctionBehavior.Normal
				), allArguments);

			return functionCall;
		}
		private static Expression ConvertCallExpression(LinqConversionContext context, MethodInfo method, Expression arg0, Expression arg1, Expression arg2)
		{
			Expression[] allArguments = new Expression[]{
				arg0,
				arg1,
				arg2
			};

			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(
				method.Name,
				method,
				null,
				MethodCallFunctionBehavior.Normal
				), allArguments);

			return functionCall;
		}
		private static Expression ConvertCallExpression(LinqConversionContext context, MethodInfo method, Expression arg0, Expression arg1)
		{
			Expression[] allArguments = new Expression[]{
				arg0,
				arg1
			};

			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(
				method.Name,
				method,
				null,
				MethodCallFunctionBehavior.Normal
				), allArguments);

			return functionCall;
		}
		private static Expression ConvertCallExpression(LinqConversionContext context, MethodInfo method, Expression arg0)
		{
			Expression[] allArguments = new Expression[] { arg0 };

			BoundFunctionCallExpression functionCall = new BoundFunctionCallExpression(new BoundMethodCallFunction(
				method.Name,
				method,
				null,
				MethodCallFunctionBehavior.Normal
				), allArguments);

			return functionCall;
		}
		private static Expression ConvertConstantExpression(LinqConversionContext context, Expression obj)
		{
			return obj;
		}
		private static Expression ConvertConstantExpression(LinqConversionContext context, Expression obj, Type type)
		{
			return obj;
		}
		private static Expression ConvertNegateExpression(LinqConversionContext context, Expression expression)
		{
			return -expression;
		}
		private static Expression ConvertNegateExpression(LinqConversionContext context, Expression expression, MethodInfo method)
		{
			return new UnaryOperationExpression(expression, ExpressionOperator.Negation, method);
		}
		private static Expression ConvertNotExpression(LinqConversionContext context, Expression expression)
		{
			return !expression;
		}
		private static Expression ConvertNotExpression(LinqConversionContext context, Expression expression, MethodInfo method)
		{
			return new UnaryOperationExpression(expression, ExpressionOperator.LogicalNot, method);
		}
		private static Expression ConvertOnesComplementExpression(LinqConversionContext context, Expression expression)
		{
			return ~expression;
		}
		private static Expression ConvertOnesComplementExpression(LinqConversionContext context, Expression expression, MethodInfo method)
		{
			return new UnaryOperationExpression(expression, ExpressionOperator.BitwiseComplement, method);
		}
		private static Expression ConvertConvertExpression(LinqConversionContext context, Expression expression, Type targetType)
		{
			return new CastExpression(expression, targetType);
		}
		private static Expression ConvertConvertExpression(LinqConversionContext context, Expression expression, Type targetType, MethodInfo method)
		{
			return new CastExpression(expression, targetType);
		}
		private static Expression ConvertDivideExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left / right;
		}
		private static Expression ConvertDivideExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.Division, method);
		}
		private static Expression ConvertLeftShiftExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.ShiftLeft);
		}
		private static Expression ConvertLeftShiftExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.ShiftLeft, method);
		}
		private static Expression ConvertRightShiftExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.ShiftRight);
		}
		private static Expression ConvertRightShiftExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.ShiftRight, method);
		}
		private static Expression ConvertModuloExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left % right;
		}
		private static Expression ConvertModuloExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.Modulus, method);
		}
		private static Expression ConvertMultiplyExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return left * right;
		}
		private static Expression ConvertMultiplyExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.Multiplication, method);
		}
		private static Expression ConvertEqualExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return Expression.Equality(left, right);
		}
		private static Expression ConvertNotEqualExpression(LinqConversionContext context, Expression left, Expression right)
		{
			return Expression.Inequality(left, right);
		}
		private static Expression ConvertNotEqualExpression(LinqConversionContext context, Expression left, Expression right, bool isLiftedToNull, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.Inequality, method);
		}
		private static Expression ConvertEqualExpression(LinqConversionContext context, Expression left, Expression right, MethodInfo method)
		{
			return new BinaryOperationExpression(left, right, ExpressionOperator.Equality, method);
		}
		private static Expression ConvertFieldExpression(LinqConversionContext context, Expression instance, FieldInfo field)
		{
			return new FieldReferenceExpression(instance, field);
		}
		private static Expression ConvertPropertyExpression(LinqConversionContext context, Expression expression, MethodInfo propertyAccessor)
		{
			return new PropertyReferenceExpression(expression, propertyAccessor.Property());
		}
		private static Expression ConvertNewExpression(LinqConversionContext context, ConstructorInfo constructor, IEnumerable<Expression> arguments, IEnumerable<MemberInfo> members)
		{
			ObjectInitializerExpression objectInitializer = new ObjectInitializerExpression(
				new UnboundNewObjectExpression(new BoundTypeReference(constructor.ReflectedType), new Expression[0]),
				arguments.Zip(members, (arg, member) => new MemberInitializer(((MethodInfo)member).Property(), arg))
				);

			return objectInitializer;
		}
		#endregion

		public static Expression TryRaiseLinqExpression(LinqConversionContext context, Expression expression)
		{
			if (context == null) throw new ArgumentNullException("context");
			if (expression == null) throw new ArgumentNullException("expression");

			{
				Expression parameter;
				if (context.ParameterVariables.TryGetValue(expression, out parameter)) return parameter;

				AssignmentExpression assignment = expression as AssignmentExpression;
				if (assignment != null)
				{
					return TryRaiseLinqExpression(context, assignment.Left);
				}
			}
			{
				ConstantExpression constantExpression = expression as ConstantExpression;
				if (constantExpression != null) return constantExpression;
			}

			MethodInfo method;
			Expression[] arguments;

			if (!TryGetMethodCallInfo(expression, out method, out arguments)) return null;

			ParameterInfo[] parameters = method.GetParameters();
			object[] raisedArguments = new object[arguments.Length + 1];
			raisedArguments[0] = context;
			for (int i = 0; i < arguments.Length; i++)
			{
				Expression argument = arguments[i];
				ParameterInfo parameter = parameters[i];

				object raisedArgument = TryRaiseTypedLinqExpression(context, argument, parameter.ParameterType);
				if (raisedArgument == null) return null;

				raisedArguments[i + 1] = raisedArgument;
			}

			Delegate converter;
			Expression raised = null;
			if (_methodConverters.TryGetValue(method, out converter))
			{
				raised = (Expression)converter.DynamicInvoke(raisedArguments);
			}

			return raised;
		}

		static IDictionary<Type, Func<LinqConversionContext, Expression, object>> _typedConverters;
		private static void InitializeTypedConverters()
		{
			Dictionary<Type, Func<LinqConversionContext, Expression, object>> converters = new Dictionary<Type, Func<LinqConversionContext, Expression, object>>();

			converters[typeof(LinqExpression)] = TryRaiseLinqExpression;
			converters[typeof(MethodInfo)] = TryRaiseLinqMethodInfo;
			converters[typeof(ConstructorInfo)] = TryRaiseLinqConstructorInfo;
			converters[typeof(IEnumerable<LinqExpression>)] = TryRaiseLinqExpressionEnumerable;
			converters[typeof(LinqExpression[])] = TryRaiseLinqExpressionEnumerable;
			converters[typeof(object)] = (context, expression) => expression;
			converters[typeof(MemberInfo)] = TryRaiseLinqMemberInfo;
			converters[typeof(Type)] = TryRaiseLinqType;

			_typedConverters = converters;
		}
		private static object TryRaiseTypedLinqExpression(LinqConversionContext context, Expression expression, Type type)
		{

			Func<LinqConversionContext, Expression, object> converter;
			if (_typedConverters.TryGetValue(type, out converter))
			{
				object converted = converter(context, expression);
				return converted;
			}
			else if (type.IsArray)
			{
				return TryRaiseLinqArray(context, expression, type.GetElementType());
			}

			return null;
		}
		private static ObjectTemplate<CastExpression> CreateLinqMethodReferenceTemplate(
			Type methodType,
			out ObjectTemplate<ConstantExpression<RuntimeMethodHandle>> methodHandleTemplate,
			out ObjectTemplate<ConstantExpression<RuntimeTypeHandle>> typeHandleTemplate
			)
		{
			methodHandleTemplate = new ObjectTemplate<ConstantExpression<RuntimeMethodHandle>>();
			typeHandleTemplate = new ObjectTemplate<ConstantExpression<RuntimeTypeHandle>>();

			var template =
				new ObjectTemplate<CastExpression>(
					new MemberTemplate[] {
						new PropertyTemplate<Type>("TargetType", new NodeEqualityTemplate<Type>(methodType, MemberEqualityComparer.Instance)),
						new PropertyTemplate<BoundFunctionCallExpression>(
							"Operand",
							new ChoiceNodeTemplate<BoundFunctionCallExpression>(
								new NodeTemplate<BoundFunctionCallExpression>[] {
									new ObjectTemplate<BoundFunctionCallExpression>(
										new MemberTemplate[] {
											new PropertyTemplate<BoundMethodCallFunction>(
												"Function",
												new ObjectTemplate<BoundMethodCallFunction>(
													new MemberTemplate[] {
														new PropertyTemplate<MethodInfo>(
															"Method",
															new NodeEqualityTemplate<MethodInfo>(((Func<RuntimeMethodHandle,MethodBase>)MethodInfo.GetMethodFromHandle).Method, MemberEqualityComparer.Instance)
															)
													})
												),
											new PropertyTemplate<Expression[]>(
												"Arguments",
												new ArrayTemplate<Expression>(
													new ElementTemplate[] {
														new ElementTemplate<ConstantExpression<RuntimeMethodHandle>>(
															0,
															methodHandleTemplate
															)
													})
											)
										}),
									new ObjectTemplate<BoundFunctionCallExpression>(
										new MemberTemplate[] {
											new PropertyTemplate<BoundMethodCallFunction>(
												"Function",
												new ObjectTemplate<BoundMethodCallFunction>(
													new MemberTemplate[] {
														new PropertyTemplate<MethodInfo>(
															"Method",
															new NodeEqualityTemplate<MethodInfo>(((Func<RuntimeMethodHandle,RuntimeTypeHandle,MethodBase>)MethodInfo.GetMethodFromHandle).Method, MemberEqualityComparer.Instance)
															)
													})
												),
											new PropertyTemplate<Expression[]>(
												"Arguments",
												new ArrayTemplate<Expression>(
													new ElementTemplate[] {
														new ElementTemplate<ConstantExpression<RuntimeMethodHandle>>(
															0,
															methodHandleTemplate
															),
														new ElementTemplate<ConstantExpression<RuntimeTypeHandle>>(
															1,
															typeHandleTemplate
															)
													})
											)
										})
								})
							)
					});

			return template;
		}

		#region Type converters
		private static Type TryRaiseLinqType(LinqConversionContext context, Expression expression)
		{
			TypeReferenceExpression typeRef = expression as TypeReferenceExpression;
			if (typeRef == null) return null;

			return typeRef.Type;
		}
		private static MethodInfo TryRaiseLinqMethodInfo(LinqConversionContext context, Expression expression)
		{
			return TryRaiseLinqMethodReference<MethodInfo>(expression);
		}
		private static MemberInfo TryRaiseLinqMemberInfo(LinqConversionContext context, Expression expression)
		{
			MethodInfo method = TryRaiseLinqMethodInfo(context, expression);
			return method;
		}
		private static ConstructorInfo TryRaiseLinqConstructorInfo(LinqConversionContext context, Expression expression)
		{
			return TryRaiseLinqMethodReference<ConstructorInfo>(expression);
		}
		private static Array TryRaiseLinqArray(LinqConversionContext context, Expression expression, Type elementType)
		{
			Array raisedArray = null;

			NewImplicitArrayExpression argumentArray = expression as NewImplicitArrayExpression;
			if (argumentArray != null)
			{
				Expression[] expressionArray = argumentArray.Arguments.ToArray();
				raisedArray = Array.CreateInstance(elementType, expressionArray.Length);
				for (int i = 0; i < expressionArray.Length; i++)
				{
					Expression elementExpression = expressionArray[i];
					object raised = TryRaiseTypedLinqExpression(context, elementExpression, elementType);
					if (raised == null) return null;
					raisedArray.SetValue(raised, i);
				}
			}
			else if (expression is NewArrayExpression)
			{
				raisedArray = Array.CreateInstance(elementType, 0);
			}

			return raisedArray;
		}
		private static IEnumerable<Expression> TryRaiseLinqExpressionEnumerable(LinqConversionContext context, Expression expression)
		{
			IEnumerable<Expression> raisedArray = null;

			NewImplicitArrayExpression argumentArray = expression as NewImplicitArrayExpression;
			if (argumentArray != null)
			{
				List<Expression> raisedExpressions = new List<Expression>();
				foreach (var elementExpression in argumentArray.Arguments)
				{
					Expression raisedExpression = TryRaiseLinqExpression(context, elementExpression);
					if (raisedExpression == null) return null;
					raisedExpressions.Add(raisedExpression);
				}
				raisedArray = raisedExpressions;
			}
			else if (expression is NewArrayExpression)
			{
				raisedArray = new Expression[0];
			}

			return raisedArray;
		}
		private static TMethod TryRaiseLinqMethodReference<TMethod>(
			Expression expression
			)
			where TMethod : MethodBase
		{
			ObjectTemplate<ConstantExpression<RuntimeMethodHandle>> methodHandleTemplate;
			ObjectTemplate<ConstantExpression<RuntimeTypeHandle>> typeHandleTemplate;

			var methodCallTemplate = CreateLinqMethodReferenceTemplate(typeof(TMethod), out methodHandleTemplate, out typeHandleTemplate);

			TMethod calledMethod = null;
			if (methodCallTemplate.Inspect(expression))
			{
				calledMethod = (TMethod)(
				(typeHandleTemplate.CapturedValue == null)
				? MethodBase.GetMethodFromHandle(methodHandleTemplate.CapturedValue.TypedValue)
				: MethodBase.GetMethodFromHandle(methodHandleTemplate.CapturedValue.TypedValue, typeHandleTemplate.CapturedValue.TypedValue)
				);
			}

			return calledMethod;
		}
		#endregion
		#endregion

		#region Queries
		private static QueryClause GetSourceClause(Expression source, TypedVariable rangeVariable)
		{
			QueryExpression query = source as QueryExpression;
			if ((query != null) && (query.RangeVariable.Name == rangeVariable.Name))
			{
				return query.Source;
			}
			else
			{
				return new FromClause(source, rangeVariable);
			}
		}
		public static QueryExpressionBase TryRaiseLinqQuery(MethodInfo queryMethod, Expression[] arguments)
		{
			QueryExpressionBase raised = null;

			MethodInfo whereMethod = ((Func<IQueryable<object>, LinqExpressions.Expression<Func<object, bool>>, IQueryable<object>>)Queryable.Where).Method.GetGenericMethodDefinition();
			MethodInfo selectMethod = ((Func<IQueryable<object>, LinqExpressions.Expression<Func<object, object>>, IQueryable<object>>)Queryable.Select).Method.GetGenericMethodDefinition();
			MethodInfo selectManyMethod = ((Func<IQueryable<object>, LinqExpressions.Expression<Func<object, IEnumerable<object>>>, LinqExpressions.Expression<Func<object, object, object>>, IQueryable<object>>)Queryable.SelectMany).Method.GetGenericMethodDefinition();
			MethodInfo orderbyMethod = ((Func<IQueryable<object>, LinqExpressions.Expression<Func<object, object>>, IEnumerable<object>>)Queryable.OrderBy).Method.GetGenericMethodDefinition();
			MethodInfo orderbyDescendingMethod = ((Func<IQueryable<object>, LinqExpressions.Expression<Func<object, object>>, IEnumerable<object>>)Queryable.OrderByDescending).Method.GetGenericMethodDefinition();
			MethodInfo groupbyMethod = ((Func<IQueryable<object>, LinqExpressions.Expression<Func<object, object>>, LinqExpressions.Expression<Func<object, object>>, IQueryable<IGrouping<object, object>>>)Queryable.GroupBy).Method.GetGenericMethodDefinition();

			if (queryMethod.IsGenericMethod)
			{
				MethodInfo queryMethodDefinition = queryMethod.GetGenericMethodDefinition();

				if (MemberEqualityComparer.Equal(queryMethodDefinition, whereMethod))
				{
					Expression source = arguments[0];
					LambdaExpression conditionLambda = arguments[1] as LambdaExpression;
					if (conditionLambda == null) return null;

					TypedVariable rangeVariable = conditionLambda.Parameters[0];

					QueryClause sourceClause = GetSourceClause(source, rangeVariable);
					WhereClause whereClause = new WhereClause(sourceClause, conditionLambda.Body);
					raised = new QueryExpression(whereClause, rangeVariable);
				}
				else if (MemberEqualityComparer.Equal(queryMethodDefinition, selectMethod))
				{
					Expression source = arguments[0];
					LambdaExpression projectionLambda = arguments[1] as LambdaExpression;
					if (projectionLambda == null) return null;
					TypedVariable rangeVariable = projectionLambda.Parameters[0];

					QueryClause sourceClause = GetSourceClause(source, rangeVariable);
					raised = new ProjectedQueryExpression(sourceClause, projectionLambda.Body);
				}
				else if (queryMethodDefinition == selectManyMethod)
				{
					Expression source = arguments[0];

					LambdaExpression enumerableLambda = arguments[1] as LambdaExpression;
					if (enumerableLambda == null) return null;

					LambdaExpression projectionLambda = arguments[2] as LambdaExpression;
					if (projectionLambda == null) return null;

					TypedVariable rangeVariable = enumerableLambda.Parameters[0];
					TypedVariable itemVariable = projectionLambda.Parameters[0];

					QueryClause sourceClause = GetSourceClause(source, rangeVariable);
					SelectManyClause selectManyClause = new SelectManyClause(sourceClause, enumerableLambda.Body, itemVariable);

					bool isProjected =
						!object.Equals(projectionLambda.Body, new BoundVariableReferenceExpression(itemVariable));

					raised = isProjected
						? (QueryExpressionBase)new ProjectedQueryExpression(selectManyClause, projectionLambda.Body)
						: new QueryExpression(selectManyClause, itemVariable)
						;
				}
				else if (
				   (queryMethodDefinition == orderbyMethod)
				   || (queryMethodDefinition == orderbyDescendingMethod)
				   )
				{
					bool descending = (queryMethodDefinition == orderbyDescendingMethod);

					Expression source = arguments[0];

					LambdaExpression sortLambda = arguments[1] as LambdaExpression;
					if (sortLambda == null) return null;

					TypedVariable rangeVariable = sortLambda.Parameters[0];

					QueryClause sourceClause = GetSourceClause(source, rangeVariable);
					SelectManyClause selectManyClause = new SelectManyClause(sourceClause, sortLambda.Body, rangeVariable);

					ListSortDirection direction = descending ? ListSortDirection.Descending : ListSortDirection.Ascending;
					OrderByClause orderClause = new OrderByClause(sourceClause, sortLambda.Body, direction);
					raised = new QueryExpression(orderClause, rangeVariable);
				}
				else if (MemberEqualityComparer.Equal(queryMethodDefinition, groupbyMethod))
				{
					Expression source = arguments[0];

					LambdaExpression keyLambda = arguments[1] as LambdaExpression;
					if (keyLambda == null) return null;

					LambdaExpression elementLambda = arguments[2] as LambdaExpression;
					if (elementLambda == null) return null;
					Expression rangeVariable = elementLambda.Body;

					//QueryClause sourceClause = GetSourceClause(source, rangeVariable);
					//GroupByClause groupByClause = new GroupByClause(sourceClause, rangeVariable, keyLambda);
					//raised = new QueryExpression(groupByClause, rangeVariable);
				}
				else
				{
				}
			}

			return raised;
		}
		#endregion
	}
}
